﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using Microsoft.DirectX;
using System.Drawing;
using DX.engine.input;
using DX.engine.exceptions;

namespace DX.engine
{
    public class Engine : IInputManager, ILogging, IEntityManager
    {
        public static Engine _Engine;
        public Form _Window;

        public bool EnableCulling = true;
        public bool EnableWireframe = true;
        public bool EnableLighting = true;
        private List<Entity> entities;
        private Dictionary<string, Model> models;
        private EngineSettings Settings;

        private Microsoft.DirectX.Direct3D.Device device;
        //private Microsoft.DirectX.DirectInput.Device keyb;
        //private Microsoft.DirectX.DirectInput.Device mouse;

        private Engine()
        {
            entities = new List<Entity>();
            models = new Dictionary<string, Model>();
        }

        private void Initialize()
        {
            Log("Initializing Engine...");
            GetEngine(); //Initialize engine if it hasn't been already.
            InitializeSettings();
            InitializeWindow();
            InitializeDevice();
            Log("Engine Initialization complete!");
        }

        /// <summary>
        /// Runs the engine.
        /// </summary>
        public static void Run(Form f)
        {
            Engine e = GetEngine();
            e.SetTargetWindow(f);
            e.Log("Starting game loop.");
            e.Initialize();
            
        }

        private void SetTargetWindow(Form win)
        {
            Log("Changing target window!");
            _Window = win;
        }

        public void InitializeWindow()
        {
            Log("Initializing Window...");
            if (_Window == null)
            {
                Log("FATAL ERROR: Could not get window handle!");
                throw new InitializationFailureException("Missing target window!");
            }
            Log("Window Initialization complete!");
        }

        /// <summary>
        /// Initializes our hardware device for rendering.
        /// </summary>
        public void InitializeDevice()
        {
            Log("Initializing Device...");
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.AutoDepthStencilFormat = DepthFormat.D16;
            presentParams.EnableAutoDepthStencil = true;
            presentParams.MultiSample = MultiSampleType.TwoSamples;

            device = new Microsoft.DirectX.Direct3D.Device(0, Microsoft.DirectX.Direct3D.DeviceType.Hardware, _Window, CreateFlags.SoftwareVertexProcessing, presentParams);

            Log("Device Initialization successful!");
        }

        private void InitializeSettings()
        {
            Log("Loading settings...");
            Settings = new EngineSettings();
            Log("Settings loaded!");
        }

        public void PositionCamera()
        {
            Log("Positioning camera...");
            device.VertexFormat = CustomVertex.PositionColored.Format;
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, _Window.Width / _Window.Height, 1f, 500f);
            device.Transform.View = Matrix.LookAtLH(new Vector3(0, -40, 100), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            device.RenderState.Lighting = EnableLighting;
            if (EnableLighting)
            {
                //Define our lights
                device.Lights[0].Type = LightType.Directional;
                device.Lights[0].Diffuse = Color.White;
                device.Lights[0].Direction = new Vector3(-0.5f, 0, -1f);
                device.Lights[0].Enabled = true;
            }
            if (!EnableCulling)
                device.RenderState.CullMode = Cull.None;
            else
                device.RenderState.CullMode = Cull.CounterClockwise;
            if (EnableWireframe)
                device.RenderState.FillMode = FillMode.WireFrame;
            else
                device.RenderState.FillMode = FillMode.Solid;
            Log("Finished positioning camera.");
        }

        private static Engine GetEngine()
        {
            if (_Engine == null)
                _Engine = new Engine();

            return _Engine;
        }

        public static EngineSettings GetSettings()
        {
            return GetEngine().Settings;
        }

        public static IInputManager GetInputEngine()
        {
            return GetEngine();
        }

        public static ILogging GetLoggingEngine()
        {
            return GetEngine();
        }

        public static IEntityManager GetEntityEngine()
        {
            return GetEngine();
        }

        public void Spawn(Entity ent)
        {
            if (models.ContainsKey(ent.GetModel()))
                entities.Add(ent);
            else
                Log("Failed to spawn entity:" + ent.ToString());
        }

        public int GetNextEntityID()
        {
            int hi = 0;
            foreach (Entity e in entities)
                if (e.GetEntityID() > hi)
                    hi = e.GetEntityID();
            return hi++;
        }

        private void Log(string msg)
        {
            System.Diagnostics.Debug.WriteLine(DateTime.Now.ToShortTimeString() + ": " + msg);
        }

        Microsoft.DirectX.DirectInput.Device IInputManager.GetKeyboard()
        {
            throw new NotImplementedException();
        }

        Microsoft.DirectX.DirectInput.Device IInputManager.GetMouse()
        {
            throw new NotImplementedException();
        }

        void ILogging.Log(string msg)
        {
            Log(msg);
        }

        int IEntityManager.GetNextEntityID()
        {
            return GetNextEntityID();
        }
    }
}
